﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Linq;

namespace Update
{
    public partial class MainWindow : Form
    {
        public const string SoftwareName = "ArtMath", OfficialWebsite = "http://47.103.48.111", 
            UpdateSourcePath = OfficialWebsite + "/" + SoftwareName + "/Update";
        public static readonly string TempPath = Path.GetTempPath(), RootPath, AdministratorFlag;
        enum OperationType
        {
            New,
            Update,
            Delete,
        }
        class UpdateFileInfo
        {
            public string Source, Version, Author, Description, Destination, Size, UpdateTime;
            public OperationType OperationType;
            public ListViewItem Item;
            public bool Success;
        }
        static MainWindow()
        {
            DirectoryInfo di = new DirectoryInfo(Application.StartupPath);
            while (!di.Name.Equals(SoftwareName, StringComparison.OrdinalIgnoreCase) && !containsFile(di, $"{SoftwareName}.Window.exe"))
            {
                di = di.Parent;
            }
            RootPath = di.FullName;
            WindowsIdentity current = WindowsIdentity.GetCurrent();
            WindowsPrincipal prin = new WindowsPrincipal(current);
            if (prin.IsInRole(WindowsBuiltInRole.Administrator))
            {
                AdministratorFlag = Resources.LeftParenthesis + Resources.Translate("Administrator") + Resources.RightParenthesis;
            }
            else
            {
                AdministratorFlag = string.Empty;
            }
        }
        public MainWindow()
        {
            InitializeComponent();
            Text += AdministratorFlag;
        }
        static bool containsFile(DirectoryInfo di, string fileName)
        {
            foreach (FileInfo item in di.GetFiles())
            {
                if (item.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }
        private void bgwGetUpdateInfo_DoWork(object sender, DoWorkEventArgs e)
        {
            Program.SwitchLanguage();
            bgwGetUpdateInfo.ReportProgress(-2);
            try
            {
                WebClient wc = new WebClient();
                string tmpPath = Path.Combine(TempPath, Guid.NewGuid().ToString());
                wc.DownloadFile(UpdateSourcePath + "/update-info." + (Thread.CurrentThread.CurrentUICulture.Name == "zh-CN" ? "zh-CN" : "en-US") + ".xml", tmpPath);
                XmlDocument doc = new XmlDocument();
                doc.Load(tmpPath);
                XmlElement root = doc.DocumentElement;//<info>节点
                List<UpdateFileInfo> updateInfos = new List<UpdateFileInfo>();
                foreach (XmlNode node in root.ChildNodes)
                {
                    UpdateFileInfo info = new UpdateFileInfo();
                    foreach (XmlNode item in node.ChildNodes)
                    {
                        switch (item.Name.ToLower())
                        {
                            case "src":
                            case "source":
                                info.Source = UpdateSourcePath + "/" + item.InnerText;
                                break;
                            case "dest":
                            case "destination":
                                info.Destination = Path.Combine(RootPath, item.InnerText);
                                break;
                            case "size":
                                info.Size = item.InnerText;
                                break;
                            case "ver":
                            case "version":
                                info.Version = item.InnerText;
                                break;
                            case "author":
                                info.Author = item.InnerText.Replace("\n", Environment.NewLine);
                                break;
                            case "desc":
                            case "description":
                                info.Description = item.InnerText.Replace("\n", Environment.NewLine);
                                break;
                            case "datetime":
                                info.UpdateTime = item.InnerText;
                                break;
                        }
                    }
                    if (node.Name.Equals("obsolete", StringComparison.OrdinalIgnoreCase))
                    {
                        if (File.Exists(info.Destination))
                        {
                            info.OperationType = OperationType.Delete;
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else
                    {
                        if (File.Exists(info.Destination))
                        {
                            FileVersionInfo fv = FileVersionInfo.GetVersionInfo(info.Destination);
                            if (info.Version.CompareTo(fv.FileVersion) > 0)
                            {
                                info.OperationType = OperationType.Update;
                            }
                            else
                            {
                                try
                                {
                                    DateTime dt = File.GetLastWriteTime(info.Destination);
                                    if (DateTime.Parse(info.UpdateTime).CompareTo(dt) > 0)
                                    {
                                        info.OperationType = OperationType.Update;
                                    }
                                }
                                catch (Exception)
                                {
                                    continue;
                                }
                            }
                        }
                        else
                        {
                            info.OperationType = OperationType.New;
                        }
                    }
                    updateInfos.Add(info);
                }
                if (updateInfos.Count == 0)
                {
                    bgwGetUpdateInfo.ReportProgress(-5);
                }
                else
                {
                    bgwGetUpdateInfo.ReportProgress(-1, updateInfos.Count);
                    for (int i = 0; i < updateInfos.Count; i++)
                    {
                        bgwGetUpdateInfo.ReportProgress(i + 1, updateInfos[i].Item = new ListViewItem(new string[] { Path.GetFileName(updateInfos[i].Destination), updateInfos[i].Version, Resources.Translate(updateInfos[i].OperationType.ToString()) })
                        {
                            Tag = updateInfos[i],
                            UseItemStyleForSubItems = false,
                        });
                    }
                    bgwGetUpdateInfo.ReportProgress(-3, updateInfos);
                }
                File.Delete(tmpPath);
            }
            catch (Exception ex)
            {
                bgwGetUpdateInfo.ReportProgress(-4, ex.Message);
            }
        }

        private void MainWindow_Load(object sender, EventArgs e)
        {
            bool can = true;
            while (true)
            {
                try
                {
                    Process[] proc = Process.GetProcessesByName($"{SoftwareName}.Window");
                    if (proc.Length == 0) break;
                    DialogResult res = MessageBox.Show(string.Format(Resources.Translate("CloseProcessBeforeUpdate"), Environment.NewLine), Resources.Translate("Ask"), MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Question);
                    if (res == DialogResult.Abort)
                    {
                        foreach (Process item in proc)
                        {
                            item.Kill();
                        }
                        break;
                    }
                    else if (res == DialogResult.Retry)
                    {
                        continue;
                    }
                    else if (res == DialogResult.Ignore)
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + Environment.NewLine + Resources.Translate("UpdateFailed"), Resources.Translate("Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
                    can = false;
                    break;
                }
            }
            if (can)
            {
                bgwGetUpdateInfo.RunWorkerAsync();
            }
            else
            {
                Close();
            }
        }

        private void bgwGetUpdateInfo_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            switch (e.ProgressPercentage)
            {
                case -5:
                    lblStatus.Text = Resources.Translate("IsLatestVersion");
                    break;
                case -4:
                    lblStatus.Text = e.UserState as string;
                    lblStatus.ForeColor = Color.Red;
                    llRetry.Visible = true;
                    break;
                case -3:
                    lblStatus.Text = Resources.Translate("ClickToUpdate");
                    btnUpdate.Tag = e.UserState;
                    btnUpdate.Enabled = true;
                    break;
                case -2:
                    lblStatus.Text = Resources.Translate("GetUpdateInfo") + "...";
                    break;
                case -1:
                    progressBar1.Maximum = (int)e.UserState;
                    break;
                default:
                    progressBar1.Value = e.ProgressPercentage;
                    listView1.Items.Add(e.UserState as ListViewItem);
                    break;
            }
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void btnUpdate_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 0;
            bgwUpdate.RunWorkerAsync(btnUpdate.Tag);
        }

        private void listView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (listView1.SelectedItems.Count == 1)
            {
                UpdateFileInfo info = listView1.SelectedItems[0].Tag as UpdateFileInfo;
                tbDescription.Text =
                    (info.OperationType == OperationType.Delete ? string.Empty : Resources.Translate(nameof(UpdateFileInfo.Description)) + Resources.ColonSeparate + Environment.NewLine + info.Description + Environment.NewLine) +
                    Resources.Translate(nameof(UpdateFileInfo.Author)) + Resources.ColonSeparate + Environment.NewLine + info.Author + Environment.NewLine +
                    (info.OperationType == OperationType.Delete ? string.Empty : Resources.Translate(nameof(UpdateFileInfo.Size)) + Resources.ColonSeparate + Environment.NewLine + info.Size + Environment.NewLine) +
                    Resources.Translate(nameof(UpdateFileInfo.UpdateTime)) + Resources.ColonSeparate + Environment.NewLine + info.UpdateTime;
            }
            else
            {
                tbDescription.Clear();
            }
        }

        private void llRetry_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (bgwGetUpdateInfo.IsBusy) return;
            lblStatus.ForeColor = Color.Black;
            bgwGetUpdateInfo.RunWorkerAsync();
            llRetry.Visible = false;
        }

        private void bgwUpdate_DoWork(object sender, DoWorkEventArgs e)
        {
            Program.SwitchLanguage();
            IList<UpdateFileInfo> infos = e.Argument as IList<UpdateFileInfo>;
            WebClient wc = new WebClient();
            bool hasSuccess = false, hasFail = false;
            for (int i = 0; i < infos.Count; i++)
            {
                string filename = Path.GetFileName(infos[i].Destination);
                bgwUpdate.ReportProgress(-1, filename);
                try
                {
                    if (infos[i].OperationType == OperationType.Delete)
                    {
                        File.Delete(infos[i].Destination);
                    }
                    else
                    {
                        string tmp = Path.Combine(TempPath, filename);
                        wc.DownloadFile(infos[i].Source, tmp);
                        File.Delete(infos[i].Destination);
                        File.Move(tmp, infos[i].Destination);
                    }
                    infos[i].Success = true;
                    hasSuccess = true;
                }
                catch (Exception)
                {
                    infos[i].Success = false;
                    hasFail = true;
                }
                bgwUpdate.ReportProgress(i + 1, infos[i]);
            }
            if (hasSuccess)
            {
                if (hasFail)
                {
                    bgwUpdate.ReportProgress(-4);
                }
                else
                {
                    bgwUpdate.ReportProgress(-2);
                }
            }
            else
            {
                if (hasFail)
                {
                    bgwUpdate.ReportProgress(-3);
                }
            }
        }

        private void bgwUpdate_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            switch (e.ProgressPercentage)
            {
                case -4:
                    lblStatus.Text = Resources.Translate("UpdatePartSuccess");
                    lblStatus.ForeColor = Color.Orange;
                    btnUpdate.Enabled = false;
                    break;
                case -3:
                    lblStatus.Text = Resources.Translate("UpdateFailed");
                    lblStatus.ForeColor = Color.Red;
                    btnUpdate.Enabled = false;
                    break;
                case -2:
                    lblStatus.Text = Resources.Translate("UpdateCompleted");
                    lblStatus.ForeColor = Color.Green;
                    btnUpdate.Enabled = false;
                    break;
                case -1:
                    lblStatus.Text = string.Format(Resources.Translate("UpdatingFile"), e.UserState);
                    break;
                default:
                    progressBar1.Value = e.ProgressPercentage;
                    UpdateFileInfo info = e.UserState as UpdateFileInfo;
                    info.Item.SubItems[2].Text = Resources.Translate(info.Success ? "Complete" : "Failed");
                    info.Item.SubItems[2].ForeColor = info.Success ? Color.Green : Color.Red;
                    break;
            }
        }
    }
}
